package com.springboot.jdk.socket;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * NIO网络编程
 *
 * @author: zr
 * @date: 2021-10-21
 */
public class NioServer {

    private static CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
    private static CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();

    private static ByteBuffer readBuffer;

    private static Selector selector;

    /**
     * 有界阻塞队列
     */
    private static LinkedBlockingQueue<SelectionKey> requestQueue;

    /**
     * 构建一个线程池
     */
    private static ExecutorService executorService;

    public static void main(String[] args) {
        // 初始化
        init();
        // 监听 连接请求和发送数据请求
        listen();
    }

    /**
     * 初始化
     *
     * @param
     * @return
     * @throws
     * @date 2021-10-21
     */
    private static void init() {
        // 创建一个ByteBuffer, 任务是一个读取请求数据的buffer
        readBuffer = ByteBuffer.allocate(1024);

        ServerSocketChannel serverSocketChannel;
        try {
            // 获取一个selector
            selector = Selector.open();
            // 打开 ServerSocketChannel，监听所有客户端发送过来请求建立连接的请求
            // ServerSocketChannel， 负责去跟各个客户端连接建立请求的
            serverSocketChannel = ServerSocketChannel.open();
            // NIO 就是非阻塞的
            serverSocketChannel.configureBlocking(false);
            // 监听端口号， 9000
            serverSocketChannel.socket().bind(new InetSocketAddress(9000), 100);
            // 将channel 注册到selector
            // 仅仅是关注这个ServerSocketChannel 接收到的TCP请求
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 初始化队列
        requestQueue = new LinkedBlockingQueue<>(1000);

        // 初始化线程池
        executorService = Executors.newFixedThreadPool(10);

        // 将10个Worker任务放入到线程池里面处理
        // 10个Worker 就会起10个线程，开始执行业务逻辑
        // Worker 里面的 run 方法 是 while true， 死循环，会不断的从队列里面获取任务来执行
        for (int i = 0; i < 10; i++) {
            executorService.execute(new Worker());
        }
    }

    /**
     * 监听请求
     *
     * @param
     * @return
     * @throws
     * @date 2021-10-21
     */
    public static void listen() {
        while (true) {
            try {
                // 监听 ServerSocketChannel 是否有客户端发送请求过来
                // 如果有一个客户端发送请求过来，这个select方法就会从 阻塞 出来
                // 不但的循环遍历，判断是否有请求发送过来

                // 如果 SocketChannel 接收到了请求之后，Select 就会感知到
                // 就会将 SelectionKey 交给其处理
                selector.select();

                // 从 selector 中获取到一大堆的 SelectedKeys
                // 大概可以理解 一个 Selectedkey 代表了一个请求
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                // 遍历 selectedKey，遍历所有的请求
                while (iterator.hasNext()) {
                    // 获取到 SelectionKey， 一个 Selectedkey 代表了一个请求
                    SelectionKey key = iterator.next();
                    // 删除Key，拿出来一个 SelectKey，就会删除 SelectKey
                    iterator.remove();
                    // 获取一个SelectKey出来之后，就交给 handleKey 方法处理
                    // requestQueue.offer(key);
                    handleKey(key);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * TODO
     *
     * @param null
     * @return
     * @throws
     * @date 2021-10-21
     */
    static class Worker implements Runnable {

        @Override
        public void run() {
            while (true) {
                try {
                    // 如果发现队列里面是空的，就会陷入阻塞等待状态
                    SelectionKey key = requestQueue.take();
                    // 处理请求
                    handleKey(key);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 处理请求
     * 处理请求，可以先将请求放入到队列里面，再有线程池进行处理，
     * 在这里是直接进行处理
     * 可以理解为：工作线程拿到了一个请求之后，进行处理
     *
     * @param key， 代表一个请求，可以先放到队列里面去，然后通过线程池来处理
     * @return
     * @throws
     * @date 2021-10-21
     */
    private static void handleKey(SelectionKey key) {

        SocketChannel channel = null;
        try {
            // 如果是建立一个连接的请求
            // 如果说这个key是一个 acceptable， 是一个连接请求
            if (key.isAcceptable()) {
                ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
                // 调用 accept方法，就可以跟客户端进行TCp三次握手
                // 如果三次握手成功了之后，就可以获取到一个建立好TCP连接的SocketChannel
                // 你的这SocketChannel 就是联通那个 Socket 上去，就可以进行网络数据的读写
                channel = serverSocketChannel.accept();
                if (channel != null) {
                    // 设置 SocketChannel 是非阻塞的
                    channel.configureBlocking(false);
                    // 将这个 SocketChannel 注册到 selector 上面去
                    // 这个 SocketChannel 仅仅是监听 READ 请求，就是人家发送数据过来的请求
                    // 表示我就对 读取数据 的请求感兴趣
                    channel.register(selector, SelectionKey.OP_READ);
                }
                System.out.println("【服务端】：服务端和客户端建立好了连接SocketChannel");
            } else if (key.isReadable()) {
                // 如果有连接 发送请求过来的话
                // 如果说这个key是readable， 是个发送数据过来的话，此时需要读取客户端发送过来的数据
                // 获取到一个channel
                channel = (SocketChannel) key.channel();

                ByteBuffer buffer = ByteBuffer.allocate(1024);

                // 通过底层的 Socket 读取数据，写入到 readBuffer 中
                // 读取了多少个字节的数据，position就会推进多少位， 比如  position = 21
                int count = channel.read(buffer);
                if (count > 0) {
                    // limit = 当前position， position = 0 -> limit = 21, position = =0
                    // 仅仅是读取 0~21 这段刚刚写入到 readBuffer 里面的数据
                    buffer.flip();
                    // 打印接收到的数据
                    System.out.println("【服务端】：接收到输数据，" + new String(buffer.array(), 0, count));

                    // 你接收到了客户端发送过来的数据之后，你想返回数据应该怎么办呢？
                    // TCP 是传输层协议
                    // IP 是网络层协议
                    // 以太网链路层的协议，
                    // 实际上来说你应该有一个应用层的协议， 如果是HTTP协议，人家给你发送给的数据就是下面这样的
                    // GET HTTP/1.1 /home/do.jsp
                    // ACCEPT: xxx
                    // Last-Modifid:xxx
                    //{'id':'1'}
                    // 此时就就需要按照应用层的协议，无论是HTTP协议，还是你自定义的协议
                    // 你就可以按照固定的格式来解析消息，进行处理
                    // 处理完了请求之后，你就需要发送响应回去，此时就可以重新注册一下，相当于你这个Channel刚兴趣的操作变成OP_WRITE
                    channel.register(selector, SelectionKey.OP_WRITE);
                }
            } else if (key.isWritable()) {
                // 对某个意思SocketChannel 想写一条数据回去
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                buffer.put("【服务端】：发送数据".getBytes());
                buffer.flip();

                channel = (SocketChannel) key.channel();
                channel.write(buffer);
                channel.register(selector, SelectionKey.OP_READ);
            }
        } catch (Exception e) {
            e.printStackTrace();
            if (channel != null) {
                try {
                    channel.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        }
    }
}
